/*
 * Copyright (C) 2025 Huawei. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.compose.mediaquery

import androidx.compose.runtime.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.useContents
import platform.Foundation.*
import platform.UIKit.*

/**
 * iOS actual implementation using UITraitCollection
 */
actual class PlatformMediaQuery {
    private val listenersByCondition = mutableMapOf<String, SharedMediaQueryListener>()

    actual fun matchMediaSync(condition: String): MediaQueryListener {
        listenersByCondition[condition]?.let { shared ->
            shared.addReference()
            return shared.createProxy()
        }

        val shared = SharedMediaQueryListener(
            condition = condition,
            onDestroy = { listenersByCondition.remove(condition) }
        )
        listenersByCondition[condition] = shared
        return shared.createProxy()
    }

    actual fun getListenerCount(): Int = listenersByCondition.size

    actual fun destroy() {
        listenersByCondition.values.toList().forEach { it.forceDestroy() }
        listenersByCondition.clear()
    }

    /**
     * Public method to update condition state (replaces reflection-based approach)
     * @param condition Condition string
     * @param matches New match state
     */
    fun updateCondition(condition: String, matches: Boolean) {
        listenersByCondition[condition]?.updateMatches(matches)
    }
}

private class SharedMediaQueryListener(
    val condition: String,
    private val onDestroy: () -> Unit
) {
    private val _matches = mutableStateOf(false)
    val matches: State<Boolean> = _matches
    val media: String = condition

    private var refCount = 1
    private val callbacks = mutableListOf<(Boolean) -> Unit>()

    fun addReference() {
        refCount++
    }

    fun removeReference() {
        refCount--
        if (refCount <= 0) {
            forceDestroy()
        }
    }

    fun forceDestroy() {
        callbacks.clear()
        onDestroy()
    }

    fun updateMatches(newMatches: Boolean) {
        if (_matches.value != newMatches) {
            _matches.value = newMatches
            callbacks.forEach { it(newMatches) }
        }
    }

    fun createProxy(): MediaQueryListener {
        return ProxyMediaQueryListener(this)
    }

    fun addCallback(callback: (Boolean) -> Unit) {
        callbacks.add(callback)
    }

    fun removeCallback(callback: (Boolean) -> Unit) {
        callbacks.remove(callback)
    }
}

private class ProxyMediaQueryListener(
    private val shared: SharedMediaQueryListener
) : MediaQueryListener {
    override val matches: State<Boolean> = shared.matches
    override val media: String = shared.media

    private val myCallbacks = mutableListOf<(Boolean) -> Unit>()

    override fun on(type: String, callback: (Boolean) -> Unit) {
        if (type == "change") {
            myCallbacks.add(callback)
            shared.addCallback(callback)
        }
    }

    override fun off(type: String, callback: ((Boolean) -> Unit)?) {
        if (type == "change") {
            if (callback == null) {
                myCallbacks.forEach { shared.removeCallback(it) }
                myCallbacks.clear()
            } else {
                myCallbacks.remove(callback)
                shared.removeCallback(callback)
            }
        }
    }

    override fun destroy() {
        myCallbacks.forEach { shared.removeCallback(it) }
        myCallbacks.clear()
        shared.removeReference()
    }
}

/**
 * CompositionLocal for providing PlatformMediaQuery instance
 * This replaces the global singleton to prevent memory leaks
 */
private val LocalPlatformMediaQuery = staticCompositionLocalOf<PlatformMediaQuery?> { null }

@Composable
actual fun createMediaQuery(): PlatformMediaQuery {
    // Prefer CompositionLocal provided instance, fallback to creating new one
    return LocalPlatformMediaQuery.current ?: remember { PlatformMediaQuery() }
}

@Composable
actual fun rememberMediaQuery(
    condition: String,
    onChange: ((matches: Boolean) -> Unit)?
): MediaQueryListener {
    val mediaQuery = createMediaQuery()
    val listener = remember(condition, mediaQuery) {
        mediaQuery.matchMediaSync(condition)
    }

    // State for tracking orientation and trait collection changes
    val orientationState = remember { mutableStateOf(UIDevice.currentDevice.orientation) }
    val traitCollectionState = remember { mutableStateOf(UIScreen.mainScreen.traitCollection) }

    // Register system notifications for dynamic listening
    DisposableEffect(Unit) {
        // Start generating orientation notifications
        UIDevice.currentDevice.beginGeneratingDeviceOrientationNotifications()

        // Observe orientation changes
        val orientationObserver = NSNotificationCenter.defaultCenter.addObserverForName(
            name = UIDeviceOrientationDidChangeNotification,
            `object` = null,
            queue = NSOperationQueue.mainQueue
        ) { _ ->
            orientationState.value = UIDevice.currentDevice.orientation
        }

        // Observe trait collection changes (dark mode, etc.)
        val traitObserver = NSNotificationCenter.defaultCenter.addObserverForName(
            name = UIContentSizeCategoryDidChangeNotification,
            `object` = null,
            queue = NSOperationQueue.mainQueue
        ) { _ ->
            traitCollectionState.value = UIScreen.mainScreen.traitCollection
        }

        onDispose {
            NSNotificationCenter.defaultCenter.removeObserver(orientationObserver)
            NSNotificationCenter.defaultCenter.removeObserver(traitObserver)
            UIDevice.currentDevice.endGeneratingDeviceOrientationNotifications()
        }
    }

    // Evaluate condition and update matches - Fixed dynamic listening
    LaunchedEffect(condition, orientationState.value, traitCollectionState.value) {
        val matches = evaluateMediaQuery(condition)
        // Direct call to public method - no reflection needed
        mediaQuery.updateCondition(condition, matches)
    }

    // Register onChange callback
    DisposableEffect(onChange) {
        onChange?.let {
            listener.on("change", it)
        }
        onDispose {
            onChange?.let {
                listener.off("change", it)
            }
        }
    }

    // Destroy listener when composable leaves composition
    DisposableEffect(listener) {
        onDispose {
            listener.destroy()
        }
    }

    return listener
}

/**
 * Evaluate media query condition for iOS using the parser
 */
@OptIn(ExperimentalForeignApi::class)
private fun evaluateMediaQuery(condition: String): Boolean {
    // Parse condition using the new parser
    val ast = MediaQueryParser.parse(condition) ?: return false

    // Build media context
    val device = UIDevice.currentDevice
    val screen = UIScreen.mainScreen
    val traitCollection = screen.traitCollection
    val bounds = screen.bounds
    val scale = screen.scale.toFloat()

    val widthPt = bounds.useContents { this.size.width }.toFloat()
    val heightPt = bounds.useContents { this.size.height }.toFloat()

    val mediaContext = MediaContext(
        widthDp = widthPt,  // iOS uses points, which are equivalent to dp
        heightDp = heightPt,
        densityDpi = scale * 160f,  // Convert scale to dpi (160dpi = 1x baseline)
        orientation = when (device.orientation) {
            UIDeviceOrientation.UIDeviceOrientationLandscapeLeft,
            UIDeviceOrientation.UIDeviceOrientationLandscapeRight -> Orientation.LANDSCAPE

            else -> Orientation.PORTRAIT
        },
        isDarkMode = traitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark,
        deviceType = getDeviceType(device),
        isRoundScreen = false,  // Round screens only on watchOS, not iOS
        deviceWidthDp = widthPt,
        deviceHeightDp = heightPt
    )

    // Evaluate using AST
    return evaluate(ast, mediaContext)
}

/**
 * Determine device type from UIDevice
 */
private fun getDeviceType(device: UIDevice): DeviceType {
    return when (device.userInterfaceIdiom) {
        UIUserInterfaceIdiomPhone -> DeviceType.PHONE
        UIUserInterfaceIdiomPad -> DeviceType.TABLET
        UIUserInterfaceIdiomTV -> DeviceType.TV
        UIUserInterfaceIdiomCarPlay -> DeviceType.CAR
        // Note: UIUserInterfaceIdiomWatch is only available in watchOS SDK, not iOS
        // For iOS apps, we only have Phone/Pad/TV/CarPlay
        else -> DeviceType.DEFAULT
    }
}

// CompositionLocal for shared states - nullable to allow fallback
private val LocalIsLandscapeState = compositionLocalOf<State<Boolean>?> { null }

private val LocalIsDarkModeState = compositionLocalOf<State<Boolean>?> { null }

private val LocalDeviceTypeState = compositionLocalOf<State<DeviceType>?> { null }

@Composable
actual fun ProvideMediaQueryLocals(content: @Composable () -> Unit) {
    // Create PlatformMediaQuery instance with automatic cleanup
    val platformMediaQuery = remember { PlatformMediaQuery() }

    DisposableEffect(Unit) {
        onDispose {
            platformMediaQuery.destroy()
        }
    }

    CompositionLocalProvider(
        LocalPlatformMediaQuery provides platformMediaQuery
    ) {
        // Create quick query states
        val isLandscape = rememberMediaQuery("(orientation: landscape)").matches
        val isDarkMode = rememberMediaQuery("(dark-mode: true)").matches
        val deviceType = rememberDeviceTypeInternal()

        CompositionLocalProvider(
            LocalIsLandscapeState provides isLandscape,
            LocalIsDarkModeState provides isDarkMode,
            LocalDeviceTypeState provides deviceType,
            content = content
        )
    }
}

@Composable
actual fun rememberIsLandscape(): State<Boolean> {
    return LocalIsLandscapeState.current ?: rememberMediaQuery("(orientation: landscape)").matches
}

@Composable
actual fun rememberIsDarkMode(): State<Boolean> {
    return LocalIsDarkModeState.current ?: rememberMediaQuery("(dark-mode: true)").matches
}

@Composable
actual fun rememberDeviceType(): State<DeviceType> {
    return LocalDeviceTypeState.current ?: rememberDeviceTypeInternal()
}

@Composable
private fun rememberDeviceTypeInternal(): State<DeviceType> {
    return remember {
        derivedStateOf {
            getDeviceType(UIDevice.currentDevice)
        }
    }
}
